﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Mp3Entity.cs" company="GT">
//   Gunther Taborga
// </copyright>
// <summary>
//   Defines the Mp3Entity type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace BatchID3.Mp3
{
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;

    using BatchID3.Extensions;

    using TagLib;

    public class Mp3Entity : IMp3Entity
    {
        private readonly IMp3FileSystemManager mp3FileSystemManager;

        private string album;

        private string albumArtist;

        private string artist;

        private uint beatsPerMinute;

        private File file;

        private string filePath;

        private string title;

        private uint track;

        public Mp3Entity(IMp3FileSystemManager mp3FileSystemManager, string mp3FilePath)
        {
            this.mp3FileSystemManager = mp3FileSystemManager;
            this.filePath = mp3FilePath;

            if (!this.mp3FileSystemManager.FileSystem.FileExists(mp3FilePath))
            {
                return;
            }

            this.ApplyDefaultValues();
            var tagLibFile = TagLib.File.Create(mp3FilePath);
            this.SaveId3Info(tagLibFile);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [DefaultValue("")]
        public string Album
        {
            get
            {
                return this.album;
            }

            set
            {
                if (value == this.album)
                {
                    return;
                }

                this.album = value;
                if (this.file != null)
                {
                    this.file.Tag.Album = this.album;
                    this.WriteToFile();
                }

                this.NotifyPropertyChanged("Album");
            }
        }

        [DefaultValue("")]
        public string AlbumArtist
        {
            get
            {
                return this.albumArtist;
            }

            set
            {
                if (value == this.albumArtist)
                {
                    return;
                }

                this.albumArtist = value;
                if (this.file != null)
                {
                    this.file.Tag.AlbumArtists = new[] { this.albumArtist };
                    this.WriteToFile();
                }

                this.NotifyPropertyChanged("AlbumArtist");
            }
        }

        [DefaultValue("")]
        public string Artist
        {
            get
            {
                return this.artist;
            }

            set
            {
                if (value == this.artist)
                {
                    return;
                }

                this.artist = value;
                if (this.file != null)
                {
                    this.file.Tag.Performers = new[] { this.artist };
                    this.WriteToFile();
                }

                this.NotifyPropertyChanged("Artist");
            }
        }

        [DefaultValue((uint)0)]
        public uint BeatsPerMinute
        {
            get
            {
                return this.beatsPerMinute;
            }

            set
            {
                if (value == this.beatsPerMinute)
                {
                    return;
                }

                this.beatsPerMinute = value;
                if (this.file != null)
                {
                    this.file.Tag.BeatsPerMinute = this.beatsPerMinute;
                    this.WriteToFile();
                }

                this.NotifyPropertyChanged("BeatsPerMinute");
            }
        }

        [DefaultValue("")]
        public string Duration
        {
            get
            {
                if (this.file == null)
                {
                    return string.Empty;
                }

                var duration = this.file.Properties.Duration;
                return string.Format(
                    "{0}:{1}:{2}", 
                    duration.Hours.ToString("D2"), 
                    duration.Minutes.ToString("D2"), 
                    duration.Seconds.ToString("D2"));
            }
        }

        [DefaultValue("")]
        public string FilePath
        {
            get
            {
                return this.filePath;
            }

            set
            {
                if (value == this.filePath)
                {
                    return;
                }

                this.filePath = value;
                if (this.mp3FileSystemManager.FileSystem.FileExists(this.filePath))
                {
                    this.file = TagLib.File.Create(this.filePath);
                }

                this.NotifyPropertyChanged("FilePath");
            }
        }

        [DefaultValue("")]
        public string Title
        {
            get
            {
                return this.title;
            }

            set
            {
                if (value == this.title)
                {
                    return;
                }

                this.title = value;
                if (this.file != null)
                {
                    this.file.Tag.Title = this.title;
                    this.WriteToFile();
                }

                this.NotifyPropertyChanged("Title");
            }
        }

        [DefaultValue((uint)0)]
        public uint Track
        {
            get
            {
                return this.track;
            }

            set
            {
                if (value == this.track)
                {
                    return;
                }

                this.track = value;
                if (this.file != null)
                {
                    this.file.Tag.Track = this.track;
                    this.WriteToFile();
                }

                this.NotifyPropertyChanged("Track");
            }
        }

        public override string ToString()
        {
            var tagInfo =
                new List<string>
                    {
                        this.Track.ToString(CultureInfo.InvariantCulture), 
                        this.Artist, 
                        this.Album, 
                        this.Title
                    }.Where(t => !string.IsNullOrEmpty(t));

            return string.Join(" - ", tagInfo);
        }

        private static void AddTag(TagLib.NonContainer.Tag tag, TagTypes tagType, Tag tagToAdd)
        {
            tag.StartTag.AddTag(tagType, tagToAdd);
        }

        private void NotifyPropertyChanged(string info)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        private void SaveId3Info(File tagLibFile)
        {
            var tag = tagLibFile.Tag as TagLib.NonContainer.Tag;
            var id3V1Tag = tagLibFile.GetTag(TagLib.TagTypes.Id3v1) as TagLib.Id3v1.Tag;
            var id3V2Tag = tagLibFile.GetTag(TagLib.TagTypes.Id3v2) as TagLib.Id3v2.Tag;

            switch (tagLibFile.TagTypesOnDisk)
            {
                case TagLib.TagTypes.Id3v1:
                    AddTag(tag, TagLib.TagTypes.Id3v2, id3V2Tag);
                    break;
                case TagLib.TagTypes.Id3v2:
                    AddTag(tag, TagLib.TagTypes.Id3v1, id3V1Tag);
                    break;
                case TagLib.TagTypes.None:
                    AddTag(tag, TagLib.TagTypes.Id3v1, new TagLib.Id3v1.Tag());
                    AddTag(tag, TagLib.TagTypes.Id3v2, new TagLib.Id3v2.Tag());
                    this.WriteToFile();
                    tagLibFile = TagLib.File.Create(tagLibFile.Name);
                    break;
            }

            this.SetAndNormalizeFile(tagLibFile, id3V1Tag, id3V2Tag);
            this.WriteToFile();
        }

        private void SetAndNormalizeFile(
            File tagLibFile, 
            TagLib.Id3v1.Tag id3V1Tag, 
            TagLib.Id3v2.Tag id3V2Tag)
        {
            this.file = tagLibFile;
            this.filePath = this.mp3FileSystemManager.FileSystem.GetActualPath(tagLibFile.Name);

            if (!string.IsNullOrEmpty(id3V2Tag.FirstPerformer))
            {
                this.artist = id3V2Tag.FirstPerformer;
            }

            if (!string.IsNullOrEmpty(id3V2Tag.Album))
            {
                this.album = id3V2Tag.Album;
            }

            if (!string.IsNullOrEmpty(id3V2Tag.FirstAlbumArtist))
            {
                this.albumArtist = id3V2Tag.FirstAlbumArtist;
            }
            else if (!string.IsNullOrEmpty(id3V2Tag.FirstPerformer))
            {
                this.albumArtist = id3V2Tag.FirstPerformer;
            }

            if (!string.IsNullOrEmpty(id3V2Tag.Title))
            {
                this.title = id3V2Tag.Title;
            }

            if (id3V2Tag.Track > 0)
            {
                this.track = id3V2Tag.Track;
            }

            if (id3V1Tag != null)
            {
                id3V1Tag.Performers = id3V2Tag.Performers;
                id3V1Tag.AlbumArtists = !string.IsNullOrEmpty(id3V2Tag.FirstAlbumArtist)
                                            ? new[] { id3V2Tag.FirstAlbumArtist }
                                            : new[] { id3V2Tag.FirstPerformer };
                id3V1Tag.Album = this.album;
                id3V1Tag.Title = this.title;
                id3V1Tag.Track = this.track;
            }
        }

        private void WriteToFile()
        {
            if (this.file != null && this.file.Writeable)
            {
                this.file.Save();
            }
        }
    }
}